/*
Abstract:
  IO streams workers implementation

Last changed:
  $Id: streams.cpp 67 2009-04-13 08:26:23Z vitamin.caig $

Author:
  (C) Vitamin/CAIG/2001

  This file is the part of Textator project
*/

#include "streams.h"

#include <error.h>
#include <error_codes.h>
#include <symbol_check.h>

#include <vector>
#include <fstream>
#include <iostream>

#include <text.h>

#define FILE_TAG D3CA5499

namespace
{
  const std::streamsize MAX_LINE_LEN = 1024;
  const String::value_type STDIN_FILE[] = {'-', '\0'};
  const String::value_type STDIN_NAME[] = {'<', 's', 't', 'd', 'i', 'n', '>', '\0'};

  class StdInStream : public IO::SourceStream
  {
    typedef std::vector<String::value_type> BufType;
  public:
    StdInStream() : Line(0)
    {
    }

    virtual void GetPosition(String& pos) const
    {
      pos = StringFormatter::Process(TEXT_FORMAT_SOURCE_POSITION, GetName(), Line);
    }

    virtual bool GetLine(String& line, bool asIs = false)
    {
      InStream& str(GetStream());
      line.clear();
      line.reserve(MAX_LINE_LEN);
      enum
      {
        CR = 1,
        LF = 2
      };
      unsigned finished(0);
      while (str.good())
      {
        int sym(str.peek());
        if (InStream::traits_type::eof() == sym)
        {
          break;
        }
        if (Symbol::IsLineEnd(sym))
        {
          unsigned flag = ('\r' == sym) ? LF : CR;
          if (finished & flag)
          {
            break;
          }
          finished |= flag;
          if (!asIs)
          {
            str.get();
            continue;
          }
        }
        else
        {
          if (finished)
          {
            break;
          }
        }
        line.push_back(str.get());
      }
      if (finished || !line.empty())
      {
        ++Line;
        return true;
      }
      return false;
    }

  protected:
    virtual InStream& GetStream() = 0;

  private:
    std::size_t Line;
  };

  class CInStream : public StdInStream
  {
  public:
    CInStream() : StdInStream()
    {
    }

    virtual const String& GetName() const
    {
      static const String Name(STDIN_NAME);
      return Name;
    }

  protected:
    virtual InStream& GetStream()
    {
      return StdIn;
    }
  };

  class FInStream : public StdInStream
  {
  public:
    explicit FInStream(const String& name) : StdInStream(), Name(name), FStream(ToStdString(Name).c_str(), std::ios::binary)
    {
      if (!FStream.is_open())
      {
        throw Error(ERROR_DETAIL, ERROR_OPEN_FILE, StringFormatter::Process(TEXT_ERROR_OPEN_FILE, name));
      }
    }

    virtual const String& GetName() const
    {
      return Name;
    }
  protected:
    virtual InStream& GetStream()
    {
      return FStream;
    }
  private:
    const String Name;
    std::basic_ifstream<String::value_type> FStream;
  };


  class StdOutStream : public IO::TargetStream
  {
  public:
    StdOutStream()
    {
    }

    virtual void PutString(const String& str)
    {
      GetStream() << str;
    }

  protected:
    virtual OutStream& GetStream() = 0;
  };

  class COutStream : public StdOutStream
  {
  public:
    COutStream() : StdOutStream()
    {
    }
  protected:
    virtual OutStream& GetStream()
    {
      return StdOut;
    }
  };

  class FOutStream : public StdOutStream
  {
  public:
    explicit FOutStream(const String& name) : StdOutStream(), FStream(ToStdString(name).c_str())
    {
      if (!FStream.is_open())
      {
        throw Error(ERROR_DETAIL, ERROR_OPEN_FILE, StringFormatter::Process(TEXT_ERROR_OPEN_FILE, name));
      }
    }
  protected:
    virtual OutStream& GetStream()
    {
      return FStream;
    }
  private:
    std::basic_ofstream<String::value_type> FStream;
  };
}

namespace IO
{
  SourceStream::Ptr SourceStream::Create(const String& name)
  {
    return SourceStream::Ptr(name == STDIN_FILE ?
           static_cast<SourceStream*>(new CInStream())
           :
           static_cast<SourceStream*>(new FInStream(name)));
  }

  TargetStream::Ptr TargetStream::Create(const String& name)
  {
    return TargetStream::Ptr(name.empty() ?
          static_cast<TargetStream*>(new COutStream())
          :
          static_cast<TargetStream*>(new FOutStream(name)));
  }
}
